/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */

import { strict as assert } from "node:assert";
import { writeFile } from "node:fs/promises";
import path from "node:path";
import { Flags } from "@oclif/core";
import { format as prettier } from "prettier";

import { releaseGroupFlag } from "../../flags.js";
import { BaseCommand, DEFAULT_CHANGESET_PATH, loadChangesets } from "../../library/index.js";

const DEFAULT_FILE = "UPCOMING.md";

/**
 * Generates a summary of all changesets and outputs the results to a file. This is used to generate an UPCOMING.md file
 * that provides a single place where developers can see upcoming changes.
 */
export default class GenerateUpcomingCommand extends BaseCommand<
	typeof GenerateUpcomingCommand
> {
	static readonly summary =
		`Generates a summary of all changesets. This is used to generate an UPCOMING.md file that provides a single place where developers can see upcoming changes.`;

	// This command is deprecated and will be removed in 0.53.0.
	static readonly state = "deprecated";
	static readonly deprecationOptions = {
		// The version in which the deprecated command will be removed.
		version: "0.53.0",
		// The replacement command.
		to: "generate releaseNotes",
	};

	// Enables the global JSON flag in oclif.
	static readonly enableJsonFlag = true;

	static readonly flags = {
		releaseGroup: releaseGroupFlag({
			required: true,
		}),
		releaseType: Flags.custom<"major" | "minor">({
			char: "t",
			description: "The type of release for which the upcoming file is being generated.",
			options: ["major", "minor"],
			required: true,
			parse: async (input) => {
				if (input === "major" || input === "minor") {
					return input;
				}

				throw new Error(`Invalid version bump type: ${input}`);
			},
		})(),
		out: Flags.file({
			description: `Output the results to this file.`,
			default: DEFAULT_FILE,
		}),
		...BaseCommand.flags,
	} as const;

	static readonly examples = [
		{
			description: `Generate UPCOMING.md for the client release group using the minor changesets.`,
			command: "<%= config.bin %> <%= command.id %> -g client -t minor",
		},
		{
			description: `You can output a different file using the --out flag.`,
			command: "<%= config.bin %> <%= command.id %> -g client -t minor --out testOutput.md",
		},
	];

	public async run(): Promise<string> {
		const context = await this.getContext();
		const { flags, logger } = this;

		const releaseGroup = context.repo.releaseGroups.get(flags.releaseGroup);
		if (releaseGroup === undefined) {
			this.errorLog(`Unknown release group: ${flags.releaseGroup}`);
			this.exit(2);
		}

		const changesetDir = path.join(releaseGroup.directory, DEFAULT_CHANGESET_PATH);
		const changes = await loadChangesets(changesetDir, logger);

		const { version } = releaseGroup;
		const header = `<!-- THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -->`;
		const intro = `# Upcoming changes in Fluid Framework v${version}`;

		this.info(`Loaded ${changes.length} changes.`);
		assert(flags.releaseType !== undefined, `Release type must be provided.`);

		let body: string = "";
		for (const change of changes) {
			if (change.changeTypes.includes("minor") || flags.releaseType === "major") {
				body += `## ${change.summary}\n\n${change.body}\n\n`;
			} else {
				this.info(
					`Excluding changeset: ${path.basename(change.sourceFile)} because it has no ${
						flags.releaseType
					} changes.`,
				);
			}
		}

		const contents = `${header}\n\n${intro}\n\n${body}`;
		const outputPath = path.join(context.repo.resolvedRoot, flags.out);
		this.info(`Writing output file: ${outputPath}`);
		await writeFile(
			outputPath,
			await prettier(contents, { proseWrap: "never", parser: "markdown" }),
		);

		return contents;
	}
}
